package Agent;

import java.io.File;

import java.io.FileWriter;
import java.io.IOException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;

import Exception.ExceptionNotRelevant;
import Exception.ExceptionVM;
import MachineLearning.LinearRegression;
import SLA.SLA;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import com.mysql.jdbc.Connection;
import com.mysql.jdbc.ResultSet;
import com.mysql.jdbc.Statement;

public class ImplemAgent extends UnicastRemoteObject implements Agent{
	
	//variaveis de conexão com o banco
	private Connection conn;
	private Statement statement;
	private ResultSet result; 
	
	//dados do agente
	private String ip;
	private String agentName;
	private int port;
	
	//dados referentes a carga de entrada, consideramos inicialmente uma consulta para cada cliente
	private ArrayList<String> transaction;
	private double totalTime;
	private double timeQueriesAlreadyExecuted; //tempo que levou pra executar a subconsulta
	private double avarageFlow;
	private int tuplesToReturn;
	private SLA sla;
	private LinearRegression machine;
	private float timeMonitoring;
		
	//agente esta executando uma carga
	private boolean on;
	
	protected ImplemAgent(String ip, String agentName,int port, LinearRegression machine) throws ClassNotFoundException, SQLException, IOException {
		super();
		this.ip = ip;
		this.agentName = agentName;
		this.port = port;
		
		setUpAgent();
		
		//o agente se registra
		Registry registry = LocateRegistry.createRegistry(port);
        registry.rebind(agentName, this);
        System.out.println(agentName+" Registrado!");         
        connectDB();
        this.machine = machine; 
        
	}
	
	@Override
	public void setUpAgent() {
		totalTime = 0;
		timeQueriesAlreadyExecuted = 0;
		tuplesToReturn = 0;
		transaction = new ArrayList<String>();
		avarageFlow = 0;
		timeMonitoring = 0;
		on = true;
		
	}

	private void connectDB() throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.jdbc.Driver");
	    setConn((Connection) DriverManager.getConnection("jdbc:mysql://localhost:3306/tpch?autoReconnect=true","tpch","root"));
	    DriverManager.setLoginTimeout(10000);
	    System.out.println("Agente conectou-se ao banco!");	
	    setStatement((Statement) getConn().createStatement());
	}

	private int selectivityWorkload() throws SQLException{
		int selectivityWorkload = 0;
		setStatement((Statement) getConn().createStatement());
		int numberQueries = transaction.size();	
		
		for(int i=0; i< numberQueries;i++){
			setResult((ResultSet) getStatement().executeQuery("explain "+transaction.get(i)));
			getResult().first();
			selectivityWorkload += machine.theta0 + machine.theta1*getResult().getInt("rows"); 
		}
		return selectivityWorkload;		
	}

	@Override
	public void query()  {
		long startTime;
		long finishTime=0;
		String query;
		int selectivity;
		double maximumThr;
		
		Format fmt = Format.getPrettyFormat();
	    fmt.setIndent( " " );

	    try{
            Document doc = new Document();
            Element root = new Element("node");
            doc.setRootElement(root);
            
            root.setAttribute("provisionamento", "estatico");
            root.setAttribute("id", agentName);
            //TODO PASSAR COMO PARAMETRO A CONSULTA E O ID DELA
            root.setAttribute("queryID", "default");
            root.setAttribute("selectivity",new Integer(sla.getSelectivityQ()).toString());
        	root.setAttribute("maxThroughput",new Double(sla.getMaximumThr()).toString());
               
            File f = new File(agentName+".xml");
            FileWriter arquivo = new FileWriter(f);

            Element e;
            Element e1;
            Element e2;
            							
			e = new Element("workload");				
			
			maximumThr = sla.getMaximumThr();
			System.out.println("Vazao maxima da VM "+maximumThr);
									
			selectivity = selectivityWorkload();
			System.out.println("Seletividade da consulta Q "+selectivity);
							
			tuplesToReturn = selectivity; //quantidade de tuplas a serem retornadas
			System.out.println("Tuplas a serem retornadas "+tuplesToReturn);
			e.setAttribute("workloadSelectivity",new Integer(tuplesToReturn).toString());
			root.addContent(e);			
			
			setOn(false);
			
			int i = 0, j=0;
							
			while(!transaction.isEmpty()){		
				e1 = new Element("partition");
				e1.setAttribute("id", new Integer(j).toString());
				
				query = transaction.remove(i);
				e1.setAttribute("query", query);
				
				e2 = new Element("beginTime");					
				startTime=System.currentTimeMillis();
				System.out.println("Tempo de inicio da subconsulta "+startTime);
				e2.addContent(new Double(0.001*(startTime)).toString());
				e1.addContent(e2);
				
				//executa a consulta
				System.out.println("Executando a subconsulta "+query);
				setResult((ResultSet) getStatement().executeQuery(query));
				this.result.last();
				
				int rows = this.result.getRow();
				System.out.println("Numero de tuplas retornadas "+rows);
				e2 = new Element("returnedTuples");
				e2.addContent(String.valueOf(rows));
				e1.addContent(e2);
				result.close();
				
				finishTime = System.currentTimeMillis();
				System.out.println("Tempo de fim da subconsulta "+finishTime);
				e2 = new Element("endTime");
				e2.addContent(new Double(0.001*(finishTime)).toString());
				e1.addContent(e2);
				
				/**************************************************************************/
				
				timeQueriesAlreadyExecuted += new Double(0.001*(finishTime - startTime));
				System.out.println("Tempo ja gasto na execucao das subconsultas "+timeQueriesAlreadyExecuted);
				e2 = new Element("timeQueriesAlreadyExecuted");
				e2.addContent(new Double(timeQueriesAlreadyExecuted).toString());
				e1.addContent(e2);

		
				tuplesToReturn = Math.abs(tuplesToReturn - rows);
				System.out.println("Tuplas a serem retornadas pelas subconsultas restantes "+tuplesToReturn);
				e2 = new Element("expectedTuples");
				e2.addContent(new Integer(tuplesToReturn).toString());
				e1.addContent(e2);

				timeMonitoring+=(new Double(0.001*(System.currentTimeMillis()-finishTime)));				
				
				totalTime = timeMonitoring + timeQueriesAlreadyExecuted + tuplesToReturn/maximumThr;
				System.out.println("Tempo total previsto: "+totalTime);
				e2 = new Element("timeExpectedTuples");
				e2.addContent(new Double(totalTime).toString());
				e1.addContent(e2);
			
				avarageFlow = selectivity/totalTime;
				System.out.println("Vazao media prevista : "+avarageFlow);
				e2 = new Element("avgThroughput");
				e2.addContent(new Double(avarageFlow).toString());
				e1.addContent(e2);
				
				System.out.println("Vazao media real: "+(selectivity-tuplesToReturn)/(timeMonitoring+timeQueriesAlreadyExecuted));
				
				if(maximumThr<avarageFlow) maximumThr = avarageFlow;
				
				j++;		
				
				e.addContent(e1);
			}
		
			avarageFlow = selectivity/totalTime;
			System.out.println("Vazao media ao final da execucao: "+avarageFlow);
			e2 = new Element("avgThroughput");
			e2.addContent(new Double(avarageFlow).toString());
			root.addContent(e2);
			
			if(avarageFlow < sla.getSloValue()){
				System.out.println("Não atendeu o SLA");
				e1 = new Element("notAttendSLA");		
				e1.setAttribute("machineCost",new Double(sla.getMachineCost()).toString());
				e1.setAttribute("monitoringCost",new Double(timeMonitoring*sla.getCostMonitoring()).toString());
				root.addContent(e1);					
			}

			XMLOutputter xout = new XMLOutputter(fmt);
	        xout.output( doc , arquivo );
	        arquivo.close();
	        setOn(true);
	        return;
	}catch (SQLException e) {
			e.printStackTrace();
			System.out.println("Erro na consulta de entrada!");
	} catch (IOException e) {
		e.printStackTrace();
	}
}


	public Connection getConn() {
		return conn;
	}

	public void setConn(Connection conn) {
		this.conn = conn;
	}

	public Statement getStatement() {
		return statement;
	}

	public void setStatement(Statement statement) {
		this.statement = statement;
	}

	public ResultSet getResult() {
		return result;
	}

	public void setResult(ResultSet result) {
		this.result = result;
	}
	
	@Override
	public float getTimeMonitoring() {
		return timeMonitoring;
	}
	
	@Override
	public double getTotalTime() {
		return totalTime;
	}

	@Override
	public double getTimeQueriesAlreadyExecuted() {
		return timeQueriesAlreadyExecuted;
	}

	
	@Override
	public void setTotalTime(double totalTime) {
		this.totalTime = totalTime;
	}
	
	@Override
	public double getAvarageFlow() {
		return avarageFlow;
	}

	@Override
	public void setAvarageFlow(double avarageFlow) {
		this.avarageFlow = avarageFlow;
	}
	
	@Override
	public int getTuplesToReturn() {
		return tuplesToReturn;
	}

	@Override
	public void setTuplesToReturn(int tuplesToReturn) {
		this.tuplesToReturn = tuplesToReturn;
	}
	
	@Override
	public ArrayList<String> getTransaction() {
		return transaction;
	}
	
	@Override
	public void addTransaction(ArrayList<String> transaction) {
		this.transaction.addAll(transaction);
	}
	
	@Override
	public void addQuery(String query){
		this.transaction.add(query);
	}
	
	@Override
	public void setTransaction(ArrayList<String> arrayList) {
		transaction = arrayList;		
	}

	@Override
	public SLA getSla() {
		return sla;
	}

	@Override
	public void setSla(SLA sla) {
		this.sla = sla;
	}
	
	@Override
	public String getIp() {
		return ip;
	}
	
	@Override
	public int getPort() {
		return port;
	}
	
	@Override
	public String getAgentName() {
		return agentName;
	}

	@Override
	public boolean isOn() {
		return on;
	}

	@Override
	public void setOn(boolean on) {
		this.on = on;
	}
		
	/*Input: porta do agente, ip do agente e nome do agente*/
	
	public static void main(String[] args) throws NumberFormatException, ClassNotFoundException, SQLException, IOException {
		ImplemAgent agent = new ImplemAgent(args[0],args[1],Integer.parseInt(args[2]), new LinearRegression(Double.parseDouble(args[3]), Double.parseDouble(args[4])));
	}
	
}
